WorldGen = class()

function WorldGen:init(world)
    self.world = world
    self.length = world.length
    self.height = world.height
    self.width = world.width
    self.octaveCount = 6
    self.persistance = 0.4
    self.biomeScale = 0.01 --生物群系地形缩放
    self.baseScale = 0.035 --地形基础层缩放
    self.detailScale = 0.08 --地形细节层缩放
end

function WorldGen:genNewWorld()
    self:genNewMap()
    self:carveTunnels(20)
    -- self:genCaves(20)
end

function WorldGen:genFlatStoneWorld()
    local ys = {}
    for y = 0,world.height - 5 do
        ys[y] = Block.stone.id
    end
    self:genFlatWorld(ys)
end

function WorldGen:genFlatGrassWorld()
    local ys = {}
    ys[0] = Block.bedRock.id
    for y = 1,3 do
        ys[y] = Block.stone.id
    end
    ys[4] = Block.soil.id
    ys[5] = Block.grass.id
    self:genFlatWorld(ys)
end

function WorldGen:genFlatWorld(ys)
    local world = self.world
    for x = 0,world.xChunks - 1 do
        for z = 0,world.zChunks - 1 do
            world.chunks[x * world.zChunks + z + 1]:genFlatTerrain(ys)
        end
    end
end

function WorldGen:genTest()
    local world = self.world
    for x = world.x0,world.x1 - 1 do
        for z = world.z0,world.z1 - 1 do
            world:setBlockDirectly(x,60,z,Block.stone.id)
            world:setBlockDirectly(x,0,z,Block.bedRock.id)
        end
    end
end

function WorldGen:genNewMap()
    nm = NoiseMap(self.length,self.width)
    local biomeHeights = nm:genPerlinNoise2D(self.biomeScale,1,1)
    local baseHeights = nm:genPerlinNoise2D(self.baseScale,2,self.persistance)
    local detailHeights = nm:genPerlinNoise2D(self.detailScale,self.octaveCount,self.persistance)
    local mountainHeights = nm:genPerlinNoise(0.04,1,1)
    nm:scale(detailHeights,0.1)
    nm:scale(baseHeights,0.9)
    nm:peak(mountainHeights,0.6)
    nm:addWithFilter(mountainHeights,-0.6,0)
    nm:scale(mountainHeights,1.5)
    local heights = nm:calcAdd(baseHeights,detailHeights)
    heights = nm:calcAdd(heights,mountainHeights)
    local biomes = Biomes(self.world)
    for x = 1, self.length do
        for z = 1, self.width do
            local ya = math.floor(32 + heights[x][z] * 20)
            if ya > self.height then ya = self.height end
            local b
            if biomeHeights[x][z] > 0.45 then
                b = Biomes.grassLand
            elseif biomeHeights[x][z] > 0.3 then
                b = Biomes.desertLand
            else
                b = Biomes.ocean
            end
            biomes:gen(self.world.x0 + x - 1,self.world.z0 + z - 1,ya,b)
        end
    end
end

function WorldGen:genCaves(count)
    local l,w = self.length,self.width
    for i = 1,count do
        local x = math.random(2,l - 1)
        local z = math.random(2,w - 1)
        local y = math.random(1,35)
        self.xa = math.random(1,5)
        self.za = math.random(1,4)
        self:setBlocksAir(x,y,z,15 + math.random(0,15))
    end
end

function WorldGen:setBlocksAir(x,y,z,depth)
    if depth <= 0 then return end
    if not self:setAir(x,y,z) then return end
    self:setBlocksAir(x-1,y,z,depth - math.random(1,self.xa))
    self:setBlocksAir(x+1,y,z,depth - math.random(1,self.xa))
    self:setBlocksAir(x,y,z-1,depth - math.random(1,self.za))
    self:setBlocksAir(x,y,z+1,depth - math.random(1,self.za))
    self:setBlocksAir(x,y-1,z,depth - math.random(1,6))
    self:setBlocksAir(x,y+1,z,depth - math.random(1,9))
end

function WorldGen:setAir(x,y,z)
    if x < 1 or y < 2 or z < 1 or x > self.length or y > self.height or z > self.width then
        return false
    end
    local id = self.world:getBlock(self.world.x0 + x - 1,y - 1,self.world.z0 + z - 1)
    if id == Block.air.id or id == Block.grass.id or id == Block.water.id then
        return false
    end
    self.world:setBlockDirectly(self.world.x0 + x - 1,y - 1,self.world.z0 + z - 1,Block.air.id)
    return true
end

function WorldGen:carveTunnels(count)
    local l,w = self.length,self.width
    for i = 1,count do
        local x = math.random(2,l - 1)
        local z = math.random(2,w - 1)
        local y = math.random(1,40)
        local leng = math.random() + math.random() * 150
        local dir1 = math.random() * math.pi * 2
        local dira1 = 0
        local dir2 = math.random() * math.pi * 2
        local dira2 = 0
        
        for l = 1,leng do
            x = x + math.sin(dir1) * math.sin(dir2)
            z = z + math.cos(dir1) * math.cos(dir2)
            y = y + math.sin(dir2)
            
            dir1 = dir1 + dir1 * 0.5
            dira1 = dira1 * 0.9
            dira1 = dira1 + math.random() - math.random()
            
            dir2 = dir2 + dir2 * 0.5
            dir2 = dir2 * 0.5
            dira2 = dira2 * 0.9
            dira2 = dira2 + math.random() - math.random()
            
            local size = math.sin(l * math.pi / leng) * 2.5 + 1
            
            for xx = math.floor(x - size),math.floor(x + size) do
                for yy = math.floor(y - size),math.floor(y + size) do
                    for zz = math.floor(z - size),math.floor(z + size) do
                        local xd = xx - x
                        local yd = yy - y
                        local zd = zz - z
                        local dd = xd * xd + yd * yd * 2 + zd * zd
                        if dd < size * size then
                            self:setAir(xx,yy,zz)
                        end
                    end
                end
            end
        end
    end
end
